#include <include/x86.h>
#include <include/stdio.h>
#include <include/types.h>
#include <include/string.h>
#include <include/error.h>
#include <include/assert.h>
#include <include/memlayout.h>

#include <mm/mm.h>

#include <kernel/kclock.h>
#include <kernel/proc.h>


// These variables are set by i386_detect_memory()
static uintptr_t maxpa;	// Maximum physical address
size_t npage;				// Amount of physical memory (in pages)
static size_t basemem;		// Amount of base memory (in bytes)
static size_t extmem;		// Amount of extended memory (in bytes)

// These variables are set in mm_init()
pde_t* kernel_pgdir;		// Virtual address of boot time page directory
uintptr_t kernel_cr3;		// Physical address of boot time page directory
static char* kernel_freemem;	// Pointer to next byte of free mem

struct Page* pages;		// Virtual address of physical page array
struct Page** page_stack_base;
struct Page** page_stack_top;

// Global descriptor table.
//
// The kernel and user segments are identical (except for the DPL).
// To load the SS register, the CPL must equal the DPL.  Thus,
// we must duplicate the segments for the user and the kernel.
//
struct Segdesc gdt[] =
{
	// 0x0 - unused (always faults -- for trapping NULL far pointers)
	SEG_NULL,

	// 0x8 - kernel code segment
	[GD_KT >> 3] = SEG(STA_X | STA_R, 0x0, 0xffffffff, 0),

	// 0x10 - kernel data segment
	[GD_KD >> 3] = SEG(STA_W, 0x0, 0xffffffff, 0),

	// 0x18 - user code segment
	[GD_UT >> 3] = SEG(STA_X | STA_R, 0x0, 0xffffffff, 3),

	// 0x20 - user data segment
	[GD_UD >> 3] = SEG(STA_W, 0x0, 0xffffffff, 3),

	// 0x28 - tss, initialized in idt_init()
	[GD_TSS >> 3] = SEG_NULL
};

struct Pseudodesc gdt_pd = {
	sizeof(gdt) - 1, (unsigned long) gdt
};


static int
nvram_read(int r)
{
	return mc146818_read(r) | (mc146818_read(r+1)<<8); 
}
//
// get the memory infomation by read the CMOS.
// here we want to know the memory size.
// 
static void
i386_detect_memory(void)
{
	// CMOS tells up how many kilobytes there are
	basemem = ROUNDDOWN(nvram_read(NVRAM_BASELO)*1024,PGSIZE);
	extmem = ROUNDDOWN(nvram_read(NVRAM_EXTLO)*1024,PGSIZE);

	// Calculate the maximum physical address based on whether
	// or not there is any extended memory.
	if (extmem)
		maxpa = EXTPHYSMEM + extmem;
	else
		maxpa = basemem;

	npage = maxpa / PGSIZE;

	cprintf("Physical memory: %dK available, ", (int)(maxpa/1024));
	cprintf("base = %dK, extended = %dK\n", (int)(basemem/1024),(int)(extmem/1024));
	cprintf("there are %d pages.\n", npage);

}

//
// A simple physical memory allocator, used only a few times
// in the process of setting up the virtual memory system.
// page_alloc() is the real allocator.
//
// Allocate n bytes of physical memory aligned on an 
// align-byte boundary.  Align must be a power of two.
// Return kernel virtual address.  Returned memory is uninitialized.
//
// If we're out of memory, boot_alloc should panic.
// This function may ONLY be used during initialization,
// before the page_free_list has been set up.
// the direction:
//		-- HEAD: first align then add.
//		-- TAIL: first add then align. 
//
static void*
boot_alloc(uint32_t n, uint32_t align,int direction)
{
	extern char end[];
	void *v;

	// Initialize boot_freemem if this is the first time.
	// 'end' is a magic symbol automatically generated by the linker,
	// which points to the end of the kernel's bss segment -
	// i.e., the first virtual address that the linker
	// did _not_ assign to any kernel code or global variables.
	if (kernel_freemem == 0)
		kernel_freemem = end;

	if (direction==HEAD) {
		kernel_freemem=ROUNDUP(kernel_freemem,align);
		v=kernel_freemem;
		kernel_freemem+=n;
		return v;
	}
	else {
		return kernel_freemem=ROUNDUP(kernel_freemem+n,align);
	}
}

static void check_page_alloc();
static void page_check(void);
static void page_init();
static void boot_map_segment(pde_t *pgdir, uintptr_t la, size_t size, uintptr_t pa, int perm);
// Set up a two-level page table:
//    kernel_pgdir is its linear (virtual) address of the root
//    kernel_cr3 is the physical adresss of the root
// Then turn on paging.  Then effectively turn off segmentation.
// (i.e., the segment base addrs are set to zero).
// 
// This function only sets up the kernel part of the address space
// (ie. addresses >= UTOP).  The user part of the address space
// will be setup later.
//
// From UTOP to ULIM, the user is allowed to read but not write.
// Above ULIM the user cannot read (or write). 
void
mm_init()
{
	pde_t * pgdir;
	uint32_t cr0;
	size_t n;

	i386_detect_memory();

	pgdir = boot_alloc(PGSIZE, PGSIZE,HEAD);
	memset(pgdir, 0, PGSIZE);
	kernel_pgdir = pgdir;
	kernel_cr3 = PADDR(pgdir);
	

	// allocate the kernel page structures..
	pages = (struct Page*)boot_alloc(npage*sizeof(struct Page), PGSIZE,HEAD);
	page_stack_base = (struct Page**)boot_alloc(npage*sizeof(struct Page*),PGSIZE,TAIL);
	page_stack_top = page_stack_base;
	
	// allocate the process structures...
	procs = (struct Proc*)boot_alloc(NR_PROC*sizeof(struct Proc),PGSIZE,HEAD);

	page_init();
	check_page_alloc();
	page_check();

	// Set up virtual memory 
	
	//////////////////////////////////////////////////////////////////////
	// Map the kernel stack (symbol name "bootstack").  The complete VA
	// range of the stack, [KSTACKTOP-PTSIZE, KSTACKTOP), breaks into two
	// pieces:
	//     * [KSTACKTOP-KSTKSIZE, KSTACKTOP) -- backed by physical memory
	//     * [KSTACKTOP-PTSIZE, KSTACKTOP-KSTKSIZE) -- not backed => faults
	//     Permissions: kernel RW, user NONE
	boot_map_segment(pgdir,KSTACKTOP - KSTKSIZE,KSTKSIZE,(uintptr_t )PADDR(bootstack),PTE_W|PTE_P);

	//////////////////////////////////////////////////////////////////////
	// Map all of physical memory at KERNBASE. 
	// Ie.  the VA range [KERNBASE, 2^32) should map to
	//      the PA range [0, 2^32 - KERNBASE)
	// We might not have 2^32 - KERNBASE bytes of physical memory, but
	// we just set up the mapping anyway.
	// Permissions: kernel RW, user NONE
	boot_map_segment(pgdir,KERNBASE,~KERNBASE+1,0,PTE_W|PTE_P);

	// Map the CGA Video memory...
	// Permission:kernel RW,user RW
	//boot_map_segment(pgdir,KERNBASE+0xB8000,0x8000,0xB8000,PTE_W|PTE_U|PTE_P);


	//////////////////////////////////////////////////////////////////////
	// On x86, segmentation maps a VA to a LA (linear addr) and
	// paging maps the LA to a PA.  I.e. VA => LA => PA.  If paging is
	// turned off the LA is used as the PA.  Note: there is no way to
	// turn off segmentation.  The closest thing is to set the base
	// address to 0, so the VA => LA mapping is the identity.

	// Current mapping: VA KERNBASE+x => PA x.
	//     (segmentation base=-KERNBASE and paging is off)

	// From here on down we must maintain this VA KERNBASE + x => PA x
	// mapping, even though we are turning on paging and reconfiguring
	// segmentation.

	// Map VA 0:4MB same as VA KERNBASE, i.e. to PA 0:4MB.
	// (Limits our kernel to <4MB)
	pgdir[0] = pgdir[PDX(KERNBASE)];

	// Install page table.
	lcr3(kernel_cr3);

	// Turn on paging.
	cr0 = rcr0();
	cr0 |= CR0_PE|CR0_PG|CR0_AM|CR0_WP|CR0_NE|CR0_TS|CR0_EM|CR0_MP;
	cr0 &= ~(CR0_TS|CR0_EM);
	lcr0(cr0);

	// Current mapping: KERNBASE+x => x => x.
	// (x < 4MB so uses paging pgdir[0])

	// Reload all segment registers.
	asm volatile("lgdt gdt_pd");
	asm volatile("movw %%ax,%%gs" :: "a" (GD_UD|3));
	asm volatile("movw %%ax,%%fs" :: "a" (GD_UD|3));
	asm volatile("movw %%ax,%%es" :: "a" (GD_KD));
	asm volatile("movw %%ax,%%ds" :: "a" (GD_KD));
	asm volatile("movw %%ax,%%ss" :: "a" (GD_KD));
	asm volatile("ljmp %0,$1f\n 1:\n" :: "i" (GD_KT));  // reload cs
	asm volatile("lldt %%ax" :: "a" (0));

	// Final mapping: KERNBASE+x => KERNBASE+x => x.

	// This mapping was only used after paging was turned on but
	// before the segment registers were reloaded.
	pgdir[0] = 0;

	// Flush the TLB for good measure, to kill the pgdir[0] mapping.
	lcr3(kernel_cr3);


}

// --------------------------------------------------------------
// Tracking of physical pages.
// The 'pages' array has one 'struct Page' entry per physical page.
// Pages are reference counted, and free pages are pushed into 
// the free page stack...
// --------------------------------------------------------------

//  
// Initialize page structure and the free page stack.
// After this point, ONLY use the functions below
// to allocate and deallocate physical memory via the page_free_list,
// and NEVER use boot_alloc()
//
static void
page_init(void)
{
	uint32_t i=0;
	uint32_t n=0;
	extern char _start[];

	//  1) Mark page 0 as in use.
	//     This way we preserve the real-mode IDT and BIOS structures
	//     in case we ever need them.  (Currently we don't, but...)
	//  2) Mark the rest of base memory as free.
	//  3) Then comes the IO hole [IOPHYSMEM, EXTPHYSMEM).
	//     Mark it as in use so that it can never be allocated.      
	//  4) Then extended memory [EXTPHYSMEM, ...).
	//     Some of it is in use, some is free. Where is the kernel?
	//     Which pages are used for page tables and other data structures?

	// mark page 0 as in use.
	pages[i++].pp_ref=0;

	for (;i<IOPHYSMEM/PGSIZE;i++) {
		pages[i].pp_ref = 0;
		PUSH(page_stack_top, &pages[i]);
	}
	// the IO hole.
	for (;i<EXTPHYSMEM/PGSIZE;i++) {
		pages[i].pp_ref=1;
	}
	for (;i<PADDR(_start)/PGSIZE;i++) {
		pages[i].pp_ref=0;
		PUSH(page_stack_top, &pages[i]);
	}
	// reserve the kernel image and data structures allocated
	for (;i<PADDR(kernel_freemem+PGSIZE-1)/PGSIZE;i++) {
		pages[i].pp_ref=1;
	}
	for (;i<npage;i++) {
		pages[i].pp_ref=0;
		PUSH(page_stack_top, &pages[i]);
	}
}

//
// Allocates a physical page.
// Does NOT set the contents of the physical page to zero -
// the caller must do that if necessary.
//
// *pp_ret -- is set to point to the Page struct of the newly allocated
// page
//
// RETURNS 
//   0 -- on success
//   -E_NO_MEM -- otherwise 
//
// pp_ref should not be incremented until it is mapped...
// 
int
page_alloc(struct Page **pp_ret)
{
	if (EMPTY(page_stack_top,page_stack_base))
		return -E_NO_MEM;
	else {
		*pp_ret=POP(page_stack_top);
		memset(*pp_ret, 0, sizeof(struct Page));
		return 0;
	}
}
//
// push a page to the free page stack.
// (This function should only be called when pp->pp_ref reaches 0.)
//
void
page_free(struct Page *pp)
{
	if (pp->pp_ref!=0)
		panic("page_free: pp_ref is not zero.\n");
	PUSH(page_stack_top,pp);
}

//
// Invalidate a TLB entry, but only if the page tables being
// edited are the ones currently in use by the processor.
//
void
tlb_invalidate(pde_t *pgdir, const uintptr_t la)
{
	// Flush the entry only if we're modifying the current address space.
	// if (!curenv || curenv->env_pgdir == pgdir)
	// now there is only one address space...
		invlpg((void*)la);
}

// Given 'pgdir', a pointer to a page directory, page_pte returns
// a pointer to the page table entry (PTE) for linear address 'la'.
// This requires checking the two-level page table structure.
//
// If the relevant page table doesn't exist in the page directory, then:
//    - If alloc == 0, page_pte returns NULL.
//    - Otherwise, page_pte tries to allocate a new page table
//	with page_alloc.  If this fails, pgdir_walk returns NULL.
//    - page_pte sets pp_ref to 1 for the new page table.
//    - Finally, page_pte returns a pointer,which point to the new page table entry.
//
pte_t *
page_pte(pde_t *pgdir, const uintptr_t la, int alloc)
{
	uint32_t pde;
	struct Page *free_page;

	pde = pgdir[PDX(la)];
	if (pde & PTE_P)
		return (pte_t *)KADDR(PTE_ADDR(pde)) + PTX(la);
	else if (!alloc)
		return NULL;
	else {
		if (page_alloc(&free_page) == 0) {
			// clear the actual physical page
			memset(page2kva(free_page), 0, PGSIZE);
			// setup the specific entry
			pgdir[PDX(la)] = page2pa(free_page) | PTE_W | PTE_U | PTE_P;
			// increase the pp_ref field
			free_page->pp_ref++;
			return (pte_t *)KADDR(PTE_ADDR(pgdir[PDX(la)])) + PTX(la);
		}
		return NULL;
	}
}
//
// Map the physical page 'pp' at virtual address 'va'.
// The permissions (the low 12 bits) of the page table
//  entry should be set to 'perm|PTE_P'.
//
// Details
//   - If there is already a page mapped at 'va', remove it.
//   - If necessary, on demand, allocates a page table and inserts it into
//     'pgdir'.
//   - pp->pp_ref should be incremented if the insertion succeeds.
//   - The TLB must be invalidated if a page was formerly present at 'la'.
//
// RETURNS: 
//   0 on success
//   -E_NO_MEM, if page table couldn't be allocated
int
page_insert(pde_t *pgdir, struct Page *pp, const uintptr_t la, int perm)
{
	
	pte_t *pte;

	pte = page_pte(pgdir, la, 1);
	// truncate 'perm' to the right bits
	perm &= 0xfff;

	if (pte == NULL)
		return -E_NO_MEM;

	if (*pte & PTE_P) {
		if (PTE_ADDR(*pte) != page2pa(pp)) {
			// already a page mapped at 'la'
			page_remove(pgdir, la);
			*pte = page2pa(pp) | perm | PTE_P;
			tlb_invalidate(pgdir,la);
			// the ref is incremented, because it is used in the mapping.
			pp->pp_ref++;
		} else {
			// the same page mapped at 'la'
			// learn from 'page_check', the permission may change
			*pte = (*pte & 0xfffff000) | perm | PTE_P;
		}
	} else {
		*pte = page2pa(pp) | perm | PTE_P;
		// the ref is incremented, because it is used in the mapping.
		pp->pp_ref++;
	}

	return 0;
}
//
// Unmaps the physical page at linear address 'la'.
// If there is no physical page at that address, silently does nothing.
//
// Details:
//   - The ref count on the physical page should decrement.
//   - The physical page should be freed if the refcount reaches 0.
//   - The pg table entry corresponding to 'la' should be set to 0.
//     (if such a PTE exists)
//   - The TLB must be invalidated if you remove an entry from
//     the pg dir/pg table.
//
void
page_remove(pde_t *pgdir, const uintptr_t la)
{
	struct Page *pp;
	pte_t *pte=page_pte(pgdir,la,0);
	if (pte&&((*pte)&PTE_P)) {
		pp=&pages[PPN(PTE_ADDR(*pte))];
		if (--pp->pp_ref==0) {
			page_free(pp);
		}
		*pte=0;
		tlb_invalidate(pgdir, la);
	}
}


//
// Map [la, la+size) of linear address space to physical [pa, pa+size)
// in the page table rooted at pgdir.  Size is a multiple of PGSIZE.
// Use permission bits perm|PTE_P for the entries.
//
// This function is only intended to set up the ``static'' mappings
// above UTOP. As such, it should *not* change the pp_ref field on the
// mapped pages.
//
static void
boot_map_segment(pde_t *pgdir, uintptr_t la, size_t size, uintptr_t pa, int perm)
{
	uint32_t i;
	pte_t *ppte;
	uintptr_t pa_pp;
	for(i=0;i<size;i+=PGSIZE)
		if((ppte=page_pte(pgdir,la+i,1))!=NULL){
			pa_pp=PTE_ADDR(pa)+i;
			*ppte=pa_pp|PTE_P|perm;
		}
}
// This function returns the physical address of the page containing 'va',
// defined by the page directory 'pgdir'.  The hardware normally performs
// this functionality for us!  We define our own version to help check
// the check function; it shouldn't be used elsewhere.

static uintptr_t 
check_va2pa(pde_t *pgdir, uintptr_t la)
{
	pte_t *p;

	pgdir = &pgdir[PDX(la)];
	if (!(*pgdir & PTE_P))
		return ~0;
	p = (pte_t*) KADDR(PTE_ADDR(*pgdir));
	if (!(p[PTX(la)] & PTE_P))
		return ~0;
	return PTE_ADDR(p[PTX(la)]);
}
//
// Check the physical page allocator (page_alloc(), page_free(),
// and page_init()).
//
static void
check_page_alloc()
{
	struct Page *pp, *pp0, *pp1, *pp2;
	struct Page **ppages;
	
        // if there's a page that shouldn't be on
        // the free list, try to make sure it
        // eventually causes trouble.
	struct Page **ppsi;
	for (ppsi=page_stack_top;ppsi<page_stack_base;ppsi++){
		pp0=*ppsi;
		memset(page2kva(pp0), 0x97, 128);
	}

	// should be able to allocate three pages
	pp0 = pp1 = pp2 = 0;
	assert(page_alloc(&pp0) == 0);
	assert(page_alloc(&pp1) == 0);
	assert(page_alloc(&pp2) == 0);

	assert(pp0);
	assert(pp1 && pp1 != pp0);
	assert(pp2 && pp2 != pp1 && pp2 != pp0);
        assert(page2pa(pp0) < npage*PGSIZE);
        assert(page2pa(pp1) < npage*PGSIZE);
        assert(page2pa(pp2) < npage*PGSIZE);

	// temporarily steal the rest of the free pages
	ppages = page_stack_top;
	page_stack_top=page_stack_base;

	// should be no free memory
	assert(page_alloc(&pp) == -E_NO_MEM);

        // free and re-allocate?
        page_free(pp0);
        page_free(pp1);
        page_free(pp2);
	pp0 = pp1 = pp2 = 0;
	assert(page_alloc(&pp0) == 0);
	assert(page_alloc(&pp1) == 0);
	assert(page_alloc(&pp2) == 0);
	assert(pp0);
	assert(pp1 && pp1 != pp0);
	assert(pp2 && pp2 != pp1 && pp2 != pp0);
	assert(page_alloc(&pp) == -E_NO_MEM);

	// give free list back
	page_stack_top = ppages;

	// free the pages we took
	page_free(pp0);
	page_free(pp1);
	page_free(pp2);

	cprintf("check_page_alloc() succeeded!\n");
}

// check page_insert, page_remove, &c
static void
page_check(void)
{
	struct Page *pp, *pp0, *pp1, *pp2;
	struct Page **ppages;
	pte_t *ptep, *ptep1;
	uintptr_t va;
	int i;

	// should be able to allocate three pages
	pp0 = pp1 = pp2 = 0;
	assert(page_alloc(&pp0) == 0);
	assert(page_alloc(&pp1) == 0);
	assert(page_alloc(&pp2) == 0);

	assert(pp0);
	assert(pp1 && pp1 != pp0);
	assert(pp2 && pp2 != pp1 && pp2 != pp0);

	// temporarily steal the rest of the free pages
	ppages = page_stack_top;
	page_stack_top=page_stack_base;

	// should be no free memory
	assert(page_alloc(&pp) == -E_NO_MEM);

	// there is no free memory, so we can't allocate a page table 
	assert(page_insert(kernel_pgdir, pp1, 0x0, 0) < 0);

	// free pp0 and try again: pp0 should be used for page table
	page_free(pp0);
	assert(page_insert(kernel_pgdir, pp1, 0x0, 0) == 0);
	assert(PTE_ADDR(kernel_pgdir[0]) == page2pa(pp0));
	assert(check_va2pa(kernel_pgdir, 0x0) == page2pa(pp1));
	assert(pp1->pp_ref == 1);
	assert(pp0->pp_ref == 1);

	// should be able to map pp2 at PGSIZE because pp0 is already allocated for page table
	assert(page_insert(kernel_pgdir, pp2, PGSIZE, 0) == 0);
	assert(check_va2pa(kernel_pgdir, PGSIZE) == page2pa(pp2));
	assert(pp2->pp_ref == 1);

	// should be no free memory
	assert(page_alloc(&pp) == -E_NO_MEM);

	// should be able to map pp2 at PGSIZE because it's already there
	assert(page_insert(kernel_pgdir, pp2, PGSIZE, 0) == 0);
	assert(check_va2pa(kernel_pgdir, PGSIZE) == page2pa(pp2));
	assert(pp2->pp_ref == 1);

	// pp2 should NOT be on the free list
	// could happen in ref counts are handled sloppily in page_insert
	assert(page_alloc(&pp) == -E_NO_MEM);

	// check that pgdir_walk returns a pointer to the pte
	ptep = KADDR(PTE_ADDR(kernel_pgdir[PDX(PGSIZE)]));
	assert(page_pte(kernel_pgdir, PGSIZE, 0) == ptep+PTX(PGSIZE));

	// should be able to change permissions too.
	assert(page_insert(kernel_pgdir, pp2, PGSIZE, PTE_U) == 0);
	assert(check_va2pa(kernel_pgdir, PGSIZE) == page2pa(pp2));
	assert(pp2->pp_ref == 1);
	assert(*page_pte(kernel_pgdir, PGSIZE, 0) & PTE_U);
	
	// should not be able to map at PTSIZE because need free page for page table
	assert(page_insert(kernel_pgdir, pp0, PTSIZE, 0) < 0);

	// insert pp1 at PGSIZE (replacing pp2)
	assert(page_insert(kernel_pgdir, pp1, PGSIZE, 0) == 0);

	// should have pp1 at both 0 and PGSIZE, pp2 nowhere, ...
	assert(check_va2pa(kernel_pgdir, 0) == page2pa(pp1));
	assert(check_va2pa(kernel_pgdir, PGSIZE) == page2pa(pp1));
	// ... and ref counts should reflect this
	assert(pp1->pp_ref == 2);
	assert(pp2->pp_ref == 0);

	// pp2 should be returned by page_alloc
	assert(page_alloc(&pp) == 0 && pp == pp2);

	// unmapping pp1 at 0 should keep pp1 at PGSIZE
	page_remove(kernel_pgdir, 0x0);
	assert(check_va2pa(kernel_pgdir, 0x0) == ~0);
	assert(check_va2pa(kernel_pgdir, PGSIZE) == page2pa(pp1));
	assert(pp1->pp_ref == 1);
	assert(pp2->pp_ref == 0);

	// unmapping pp1 at PGSIZE should free it
	page_remove(kernel_pgdir, PGSIZE);
	assert(check_va2pa(kernel_pgdir, 0x0) == ~0);
	assert(check_va2pa(kernel_pgdir, PGSIZE) == ~0);
	assert(pp1->pp_ref == 0);
	assert(pp2->pp_ref == 0);

	// so it should be returned by page_alloc
	assert(page_alloc(&pp) == 0 && pp == pp1);

	// should be no free memory
	assert(page_alloc(&pp) == -E_NO_MEM);
	
	// forcibly take pp0 back
	assert(PTE_ADDR(kernel_pgdir[0]) == page2pa(pp0));
	kernel_pgdir[0] = 0;
	assert(pp0->pp_ref == 1);
	pp0->pp_ref = 0;
	
	// check pointer arithmetic in pgdir_walk
	page_free(pp0);
	va = (uintptr_t )(PGSIZE * NPDENTRIES + PGSIZE);
	ptep = page_pte(kernel_pgdir, va, 1);
	ptep1 = KADDR(PTE_ADDR(kernel_pgdir[PDX(va)]));
	assert(ptep == ptep1 + PTX(va));
	kernel_pgdir[PDX(va)] = 0;
	pp0->pp_ref = 0;
	
	// check that new page tables get cleared
	memset(page2kva(pp0), 0xFF, PGSIZE);
	page_free(pp0);
	page_pte(kernel_pgdir, 0x0, 1);
	ptep = page2kva(pp0);
	for(i=0; i<NPTENTRIES; i++)
		assert((ptep[i] & PTE_P) == 0);
	kernel_pgdir[0] = 0;
	pp0->pp_ref = 0;

	// give free list back
	page_stack_top=ppages;

	// free the pages we took
	page_free(pp0);
	page_free(pp1);
	page_free(pp2);
	
	cprintf("page_check() succeeded!\n");
}
